var Util = require("../common/util.js");

const imageMode = [ "scaleToFill", "aspectFit", "aspectFill", "widthFix", "top", "bottom", "center", "left", "right", "top left", "top right", "bottom left", "bottom right" ];

class Wxml2Canvas {
    constructor(options = {}) {
        this.device = wx.getSystemInfoSync && wx.getSystemInfoSync() || {};
        if (!options.zoom) {
            this.zoom = this.device.windowWidth / 375;
        } else {
            this.zoom = options.zoom || 1;
        }
        this.element = options.element;
        this.obj = options.obj;
        this.width = options.width * this.zoom || 0;
        this.height = options.height * this.zoom || 0;
        this.destZoom = options.destZoom || 3;
        this.destWidth = this.width * this.destZoom;
        this.destHeight = this.height * this.destZoom;
        this.translateX = options.translateX * this.zoom || 0;
        this.translateY = options.translateY * this.zoom || 0;
        this.gradientBackground = options.gradientBackground || null;
        this.background = options.background || "#ffffff";
        this.finishDraw = options.finish || function finish(params) {};
        this.errorHandler = options.error || function error(params) {};
        this.progress = options.progress || function progress(params) {};
        this.textAlign = options.textAlign || "left";
        this.fullText = options.fullText || false;
        this.font = options.font || "14px PingFang SC";
        this._init();
    }
    draw(data = {}, that) {
        let self = this;
        this.data = data;
        this.fef = that;
        this.progress(10);
        this._preloadImage(data.list).then(result => {
            this.progress(30);
            self._draw();
        }).catch(res => {
            self.errorHandler(res);
        });
    }
    measureWidth(text, font) {
        if (font) {
            this.ctx.font = font;
        }
        let res = this.ctx.measureText(text) || {};
        return res.width || 0;
    }
    _init() {
        this.progressPercent = 0;
        this.data = null;
        this.ref = null;
        this.allPic = [];
        this.screenList = [];
        this.asyncList = [];
        this.imgUrl = "";
        this.progressPercent = 0;
        this.distance = 0;
        this.progress(0);
        this.ctx = wx.createCanvasContext(this.element, this.obj);
        this.ctx.font = this.font;
        this.ctx.setTextBaseline("top");
        this.ctx.setStrokeStyle("white");
        this.debug = this.device.platform === "devtools" ? true : false;
        this._drawBakcground();
    }
    _drawBakcground() {
        if (this.gradientBackground) {
            let line = this.gradientBackground.line || [ 0, 0, 0, this.height ];
            let color = this.gradientBackground.color || [ "#fff", "#fff" ];
            let style = {
                fill: {
                    line: line,
                    color: color
                }
            };
            this._drawRectToCanvas(0, 0, this.width, this.height, style);
        } else {
            let style = {
                fill: this.background
            };
            this._drawRectToCanvas(0, 0, this.width, this.height, style);
        }
    }
    _draw() {
        let self = this;
        let list = this.data.list || [];
        let index = 0;
        let all = [];
        let count = 0;
        list.forEach(item => {
            if (item.type === "wxml") {
                count += 3;
            } else {
                count += 1;
            }
        });
        this.distance = 60 / (count || 1);
        this.progressPercent = 30;
        this.asyncList = list.filter(item => item.delay == true);
        list = list.filter(item => item.delay != true);
        drawList(list);
        Promise.all(all).then(results => {
            index = 0;
            drawList(self.asyncList, true);
            Promise.all(all).then(results => {
                self.progress(90);
                self._saveCanvasToImage();
            });
        }).catch(e => {
            console.log(e);
            self.errorHandler(e);
        });
        function drawList(list = [], noDelay) {
            list.forEach((item, i) => {
                all[index++] = new Promise((resolve, reject) => {
                    let attr = item.style;
                    item.progress = self.distance;
                    if (noDelay) {
                        item.delay = 0;
                    }
                    if (item.type === "radius-image") {
                        self._drawCircle(item, attr, resolve, reject, "image");
                    } else if (item.type === "text") {
                        self._drawText(item, attr, resolve, reject);
                    } else if (item.type === "line") {
                        self._drawLine(item, attr, resolve, reject);
                    } else if (item.type === "circle") {
                        self._drawCircle(item, attr, resolve, reject);
                    } else if (item.type === "rect") {
                        self._drawRect(item, attr, resolve, reject);
                    } else if (item.type === "image") {
                        self._drawRect(item, attr, resolve, reject, "image");
                    } else if (item.type === "wxml") {
                        self._drawWxml(item, attr, resolve, reject);
                    } else {
                        resolve();
                    }
                });
            });
        }
    }
    _saveCanvasToImage() {
        let self = this;
        setTimeout(() => {
            self.progress(95);
            let obj = {
                x: 0,
                y: 0,
                width: self.width,
                height: self.height,
                canvasId: self.element,
                success: function(res) {
                    self.progress(100);
                    self.imgUrl = res.tempFilePath;
                    self.finishDraw(self.imgUrl);
                },
                fail: function(res) {
                    self.errorHandler({
                        errcode: 1e3,
                        errmsg: "save canvas error",
                        e: res
                    });
                }
            };
            if (self.destZoom !== 3) {
                obj.destWidth = self.destWidth;
                obj.destHeight = self.destHeight;
            }
            wx.canvasToTempFilePath(obj, self.obj);
        }, self.device.system.indexOf("iOS") === -1 ? 300 : 100);
    }
    _preloadImage(list = []) {
        let self = this;
        let all = [];
        let count = 0;
        list.forEach((item, i) => {
            if (item.url && self._findPicIndex(item.url) === -1) {
                self.allPic.push({
                    url: item.url,
                    local: ""
                });
                all[count++] = new Promise((resolve, reject) => {
                    if (!/^http/.test(item.url) || /^http:\/\/(tmp)|(usr)\//.test(item.url) || /^http:\/\/127\.0\.0\.1/.test(item.url)) {
                        if (item.isBase64) {
                            let fileManager = wx.getFileSystemManager();
                            fileManager.writeFile({
                                filePath: item.url,
                                data: item.isBase64.replace(/data:image\/(.*);base64,/, ""),
                                encoding: "base64",
                                success(res) {
                                    imageInfo(item.url);
                                },
                                fail(res) {
                                    reject(res);
                                }
                            });
                        } else {
                            imageInfo(item.url);
                        }
                        function imageInfo(url) {
                            wx.getImageInfo({
                                src: url,
                                success(res) {
                                    let index = self._findPicIndex(url);
                                    if (index > -1) {
                                        self.allPic[index].local = url;
                                        self.allPic[index].width = res.width;
                                        self.allPic[index].height = res.height;
                                    }
                                    resolve({
                                        tempFilePath: url
                                    });
                                },
                                fail(res) {
                                    reject(res);
                                }
                            });
                        }
                    } else {
                        wx.downloadFile({
                            url: item.url.replace(/^https?/, "https"),
                            success: function(res) {
                                wx.getImageInfo({
                                    src: res.tempFilePath,
                                    success(img) {
                                        let index = self._findPicIndex(item.url);
                                        if (index > -1) {
                                            self.allPic[index].local = res.tempFilePath;
                                            self.allPic[index].width = img.width;
                                            self.allPic[index].height = img.height;
                                        }
                                        resolve(res);
                                    },
                                    fail(res) {
                                        reject(res);
                                    }
                                });
                            },
                            fail: res => {
                                reject({
                                    errcode: 1001,
                                    errmsg: "download pic error"
                                });
                            }
                        });
                    }
                });
            }
        });
        return Promise.all(all).then(results => {
            return new Promise(resolve => {
                resolve();
            });
        }).catch(results => {
            return new Promise((resolve, reject) => {
                reject(results);
            });
        });
    }
    _findPicIndex(url) {
        let index = this.allPic.findIndex(pic => pic.url === url);
        return index;
    }
    _drawRect(item, style, resolve, reject, isImage, isWxml) {
        let zoom = this.zoom;
        let leftOffset = 0;
        let topOffset = 0;
        let width = style.width;
        let height = style.height;
        let imgWidth = style.width;
        let imgHeight = style.height;
        let mode = null;
        try {
            item.x = this._resetPositionX(item, style);
            item.y = this._resetPositionY(item, style);
            let url;
            if (isImage) {
                let index = this._findPicIndex(item.url);
                if (index > -1) {
                    url = this.allPic[index].local;
                    imgWidth = this.allPic[index].width;
                    imgHeight = this.allPic[index].height;
                } else {
                    url = item.url;
                }
            }
            style.padding = style.padding || [];
            if (isWxml === "inline-wxml") {
                item.x = item.x + (style.padding[3] && style.padding[3] || 0);
                item.y = item.y + (style.padding[0] && style.padding[0] || 0);
            }
            leftOffset = item.x + style.width + (style.padding[1] && style.padding[1] || 0);
            if (!isWxml) {
                width = width * zoom;
                height = height * zoom;
            }
            if (style.dataset && style.dataset.mode && imageMode.indexOf(style.dataset.mode) > -1) {
                mode = {
                    type: style.dataset.mode,
                    width: imgWidth,
                    height: imgHeight
                };
            }
            this._drawRectToCanvas(item.x, item.y, width, height, style, url, mode);
            this._updateProgress(item.progress);
            if (resolve) {
                resolve();
            } else {
                return {
                    leftOffset: leftOffset,
                    topOffset: topOffset
                };
            }
        } catch (e) {
            reject && reject({
                errcode: isImage ? 1003 : 1002,
                errmsg: isImage ? "drawImage error" : "drawRect error",
                e: e
            });
        }
    }
    _drawRectToCanvas(x, y, width, height, style, url, mode) {
        let {fill: fill, border: border, boxShadow: boxShadow} = style;
        this.ctx.save();
        this._drawBoxShadow(boxShadow, res => {
            if (fill && typeof fill !== "string" && !this.debug) {
                this.ctx.setFillStyle(res.color || "#ffffff");
                this.ctx.fillRect(x, y, width, height);
            }
        });
        if (url) {
            if (mode) {
                this._resetImageByMode(url, x, y, width, height, mode);
            } else {
                this.ctx.drawImage(url, x, y, width, height);
            }
        } else {
            this._setFill(fill, () => {
                this.ctx.fillRect(x, y, width, height);
            });
        }
        this._drawBorder(border, style, border => {
            let fixBorder = border.width;
            this.ctx.strokeRect(x - fixBorder / 2, y - fixBorder / 2, width + fixBorder, height + fixBorder);
        });
        this.ctx.draw(true);
        this.ctx.restore();
    }
    _resetImageByMode(url, x, y, width, height, mode) {
        let self = this;
        let offsetX = 0;
        let offsetY = 0;
        let imgWidth = mode.width;
        let imgHeight = mode.height;
        switch (mode.type) {
          case "scaleToFill":
            imgWidth = width;
            imgHeight = height;
            self.ctx.drawImage(url, x, y, width, height);
            break;

          case "widthFix":
            height = width / ((imgWidth || 1) / (imgHeight || 1));
            self.ctx.drawImage(url, x, y, width, height);
            break;

          case "aspectFit":
            if (imgWidth > imgHeight) {
                let realHeight = width / ((imgWidth || 1) / (imgHeight || 1));
                offsetY = -(height - realHeight) / 2;
                imgWidth = width;
                imgHeight = realHeight;
            } else {
                let realWidth = height / ((imgHeight || 1) / (imgWidth || 1));
                offsetX = -(width - realWidth) / 2;
                imgWidth = realWidth;
                imgHeight = height;
            }
            _clip();
            break;

          case "aspectFill":
            if (imgWidth > imgHeight) {
                let realWidth = imgWidth / ((imgHeight || 1) / (height || 1));
                offsetX = (realWidth - width) / 2;
                imgWidth = realWidth;
                imgHeight = height;
            } else {
                let realHeight = imgHeight / ((imgWidth || 1) / (width || 1));
                offsetY = (realHeight - height) / 2;
                imgWidth = width;
                imgHeight = realHeight;
            }
            _clip();
            break;

          case "top left":
            _clip();
            break;

          case "top":
            offsetX = (mode.width - width) / 2;
            _clip();
            break;

          case "top right":
            offsetX = mode.width - width;
            _clip();
            break;

          case "left":
            offsetY = (mode.height - height) / 2;
            _clip();
            break;

          case "center":
            offsetX = (mode.width - width) / 2;
            offsetY = (mode.height - height) / 2;
            _clip();
            break;

          case "right":
            offsetX = mode.width - width;
            offsetY = (mode.height - height) / 2;
            _clip();
            break;

          case "bottom left":
            offsetY = mode.height - height;
            _clip();
            break;

          case "bottom":
            offsetX = (mode.width - width) / 2;
            offsetY = mode.height - height;
            _clip();
            break;

          case "bottom right":
            offsetX = mode.width - width;
            offsetY = mode.height - height;
            _clip();
            break;

          default:
            imgWidth = width;
            imgHeight = height;
            break;
        }
        function _clip() {
            self.ctx.save();
            self.ctx.beginPath();
            self.ctx.rect(x, y, width, height);
            self.ctx.clip();
            self.ctx.drawImage(url, x - offsetX, y - offsetY, imgWidth, imgHeight);
            self.ctx.closePath();
            self.ctx.restore();
        }
    }
    _drawText(item, style, resolve, reject, type, isWxml) {
        let zoom = this.zoom;
        let leftOffset = 0;
        let topOffset = 0;
        try {
            style.fontSize = this._parseNumber(style.fontSize);
            let fontSize = Math.ceil((style.fontSize || 14) * zoom);
            this.ctx.setTextBaseline("top");
            this.ctx.font = `${style.fontWeight ? style.fontWeight : "normal"}${fontSize}px ${style.fontFamily || "PingFang SC"}`;
            this.ctx.setFillStyle(style.color || "#454545");
            let text = item.text || "";
            let textWidth = Math.floor(this.measureWidth(text, style.font || this.ctx.font));
            let lineHeight = this._getLineHeight(style);
            let textHeight = Math.ceil(textWidth / (style.width || textWidth)) * lineHeight;
            let width = Math.ceil((style.width || textWidth) * (!isWxml ? zoom : 1));
            let whiteSpace = style.whiteSpace || "wrap";
            let x = 0;
            let y = 0;
            if (typeof style.padding === "string") {
                style.padding = Util.transferPadding(style.padding);
            }
            item.x = this._resetPositionX(item, style);
            item.y = this._resetPositionY(item, style, textHeight);
            this._drawBoxShadow(style.boxShadow);
            if (style.background || style.border) {
                this._drawTextBackgroud(item, style, textWidth, textHeight, isWxml);
            }
            if (type === "inline-text") {
                width = item.maxWidth;
                if (item.leftOffset + textWidth > width) {
                    let lineNum = Math.max(Math.floor(textWidth / width), 1);
                    let length = text.length;
                    let singleLength = Math.floor(length / lineNum);
                    let widthOffset = item.leftOffset ? item.leftOffset - item.originX : 0;
                    let {endIndex: currentIndex, single: single, singleWidth: singleWidth} = this._getTextSingleLine(text, width, singleLength, 0, widthOffset);
                    x = this._resetTextPositionX(item, style, singleWidth);
                    y = this._resetTextPositionY(item, style);
                    this.ctx.fillText(single, x, y);
                    leftOffset = x + singleWidth;
                    topOffset = y;
                    text = text.substring(currentIndex, text.length);
                    currentIndex = 0;
                    lineNum = Math.max(Math.floor(textWidth / width), 1);
                    textWidth = Math.floor(this.measureWidth(text, style.font || this.ctx.font));
                    item.x = item.originX;
                    for (let i = 0; i < lineNum; i++) {
                        let {endIndex: endIndex, single: single, singleWidth: singleWidth} = this._getTextSingleLine(text, width, singleLength, currentIndex);
                        currentIndex = endIndex;
                        if (single) {
                            x = this._resetTextPositionX(item, style, singleWidth, width);
                            y = this._resetTextPositionY(item, style, i + 1);
                            this.ctx.fillText(single, x, y);
                            if (i === lineNum - 1) {
                                leftOffset = x + singleWidth;
                                topOffset = lineHeight * lineNum;
                            }
                        }
                    }
                    let last = text.substring(currentIndex, length);
                    let lastWidth = this.measureWidth(last);
                    if (last) {
                        x = this._resetTextPositionX(item, style, lastWidth, width);
                        y = this._resetTextPositionY(item, style, lineNum + 1);
                        this.ctx.fillText(last, x, y);
                        leftOffset = x + lastWidth;
                        topOffset = lineHeight * (lineNum + 1);
                    }
                } else {
                    x = this._resetTextPositionX(item, style, textWidth, width);
                    y = this._resetTextPositionY(item, style);
                    this.ctx.fillText(item.text, x, y);
                    leftOffset = x + textWidth;
                    topOffset = lineHeight;
                }
            } else {
                if (width && textWidth > width && whiteSpace !== "nowrap") {
                    let lineNum = Math.max(Math.floor(textWidth / width), 1);
                    let length = text.length;
                    let singleLength = Math.floor(length / lineNum);
                    let currentIndex = 0;
                    if (style.lineClamp && lineNum + 1 > style.lineClamp) {
                        lineNum = style.lineClamp - 1;
                    }
                    for (let i = 0; i < lineNum; i++) {
                        let {endIndex: endIndex, single: single, singleWidth: singleWidth} = this._getTextSingleLine(text, width, singleLength, currentIndex);
                        currentIndex = endIndex;
                        x = this._resetTextPositionX(item, style, singleWidth, width);
                        y = this._resetTextPositionY(item, style, i);
                        this.ctx.fillText(single, x, y);
                    }
                    let last = text.substring(currentIndex, length);
                    let lastWidth = this.measureWidth(last);
                    if (lastWidth > width) {
                        let {single: single, singleWidth: singleWidth} = this._getTextSingleLine(last, width, singleLength);
                        lastWidth = singleWidth;
                        last = single.substring(0, single.length - 1) + "...";
                    }
                    x = this._resetTextPositionX(item, style, lastWidth, width);
                    y = this._resetTextPositionY(item, style, lineNum);
                    this.ctx.fillText(last, x, y);
                } else {
                    x = this._resetTextPositionX(item, style, textWidth, width);
                    y = this._resetTextPositionY(item, style);
                    this.ctx.fillText(item.text, x, y);
                }
            }
            this.ctx.draw(true);
            this._updateProgress(item.progress);
            if (resolve) {
                resolve();
            } else {
                return {
                    leftOffset: leftOffset,
                    topOffset: topOffset
                };
            }
        } catch (e) {
            reject && reject({
                errcode: 1004,
                errmsg: "drawText error",
                e: e
            });
        }
    }
    _drawTextBackgroud(item, style, textWidth, textHeight, isWxml) {
        if (!style.width) return;
        let zoom = isWxml ? 1 : this.zoom;
        let width = style.width || textWidth;
        let height = style.height || textHeight;
        let rectStyle = {
            fill: style.background,
            border: style.border
        };
        style.padding = style.padding || [ 0, 0, 0, 0 ];
        width += (style.padding[1] || 0) + (style.padding[3] || 0);
        height += (style.padding[0] || 0) + (style.padding[2] || 0);
        width = width * zoom;
        height = height * zoom;
        this._drawRectToCanvas(item.x, item.y, width, height, rectStyle);
    }
    _drawCircle(item, style, resolve, reject, isImage, isWxml) {
        let zoom = this.zoom;
        let r = style.r;
        try {
            item.x = this._resetPositionX(item, style);
            item.y = this._resetPositionY(item, style);
            let url;
            if (isImage) {
                let index = this._findPicIndex(item.url);
                if (index > -1) {
                    url = this.allPic[index].local;
                } else {
                    url = item.url;
                }
            }
            if (!isWxml) {
                r = r * zoom;
            }
            this._drawCircleToCanvas(item.x, item.y, r, style, url);
            this._updateProgress(item.progress);
            resolve && resolve();
        } catch (e) {
            reject && reject({
                errcode: isImage ? 1006 : 1005,
                errmsg: isImage ? "drawCircleImage error" : "drawCircle error",
                e: e
            });
        }
    }
    _drawCircleToCanvas(x, y, r, style, url) {
        let {fill: fill, border: border, boxShadow: boxShadow} = style;
        this.ctx.save();
        this._drawBoxShadow(boxShadow, res => {
            if (fill && typeof fill !== "string" || url && res.color) {
                this.ctx.setFillStyle(res.color || "#ffffff");
                this.ctx.beginPath();
                this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI);
                this.ctx.closePath();
                this.ctx.fill();
            }
        });
        if (url) {
            this.ctx.save();
            this.ctx.beginPath();
            this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI);
            this.ctx.clip();
            this.ctx.drawImage(url, x, y, r * 2, r * 2);
            this.ctx.closePath();
            this.ctx.restore();
        } else {
            this._setFill(fill, () => {
                this.ctx.beginPath();
                this.ctx.arc(x + r, y + r, r, 0, 2 * Math.PI);
                this.ctx.closePath();
                this.ctx.fill();
            });
        }
        this._drawBorder(border, style, border => {
            this.ctx.beginPath();
            this.ctx.arc(x + r, y + r, r + border.width / 2, 0, 2 * Math.PI);
            this.ctx.stroke();
            this.ctx.closePath();
        });
        this.ctx.draw(true);
        this.ctx.restore();
    }
    _drawLine(item, style, resolve, reject, isWxml) {
        let zoom = this.zoom;
        try {
            let x1 = item.x * zoom + this.translateX;
            let y1 = item.y * zoom + this.translateY;
            let x2 = item.x2 * zoom + this.translateX;
            let y2 = item.y2 * zoom + this.translateY;
            this._drawLineToCanvas(x1, y1, x2, y2, style);
            this._updateProgress(item.progress);
            resolve && resolve();
        } catch (e) {
            reject && reject({
                errcode: 1007,
                errmsg: "drawLine error",
                e: e
            });
        }
    }
    _drawLineToCanvas(x1, y1, x2, y2, style) {
        let {stroke: stroke, dash: dash, boxShadow: boxShadow} = style;
        this.ctx.save();
        if (stroke) {
            this._setStroke(stroke);
        }
        this._drawBoxShadow(boxShadow);
        if (dash) {
            let dash = [ style.dash[0] || 5, style.dash[1] || 5 ];
            let offset = style.dash[2] || 0;
            this.ctx.setLineDash(dash, offset || 0);
        }
        this.ctx.moveTo(x1, y1);
        this.ctx.setLineWidth((style.width || 1) * this.zoom);
        this.ctx.lineTo(x2, y2);
        this.ctx.stroke();
        this.ctx.draw(true);
        this.ctx.restore();
    }
    _drawImage(item, style, resolve, reject, isWxml) {
        let zoom = this.zoom;
        try {
            item.x = this._resetPositionX(item, style);
            item.y = this._resetPositionY(item, style);
            item.x = item.x + (style.padding[3] || 0);
            item.y = item.y + (style.padding[0] || 0);
            let index = this._findPicIndex(item.url);
            let url = index > -1 ? this.allPic[index].local : item.url;
            this._drawImageToCanvas(url, item.x, item.y, style.width * zoom, style.height * zoom, style);
            this._updateProgress(item.progress);
            resolve && resolve();
        } catch (e) {
            reject && reject({
                errcode: 1012,
                errmsg: "drawRect error",
                e: e
            });
        }
    }
    _drawImageToCanvas(url, x, y, width, height, style) {
        let {fill: fill, border: border, boxShadow: boxShadow} = style;
        this.ctx.save();
        this._drawBoxShadow(boxShadow);
        this.ctx.drawImage(url, x, y, width, height);
        this._drawBorder(border, style, border => {
            let fixBorder = border.width;
            this.ctx.strokeRect(x - fixBorder / 2, y - fixBorder / 2, width + fixBorder, height + fixBorder);
        });
        this.ctx.draw(true);
        this.ctx.restore();
    }
    _drawWxml(item, style, resolve, reject) {
        let self = this;
        let all = [];
        try {
            this._getWxml(item, style).then(results => {
                let sorted = self._sortListByTop(results[0]);
                let count = 0;
                let progress = 0;
                Object.keys(sorted).forEach(item => {
                    count += sorted[item].length;
                });
                progress = this.distance * 3 / (count || 1);
                all = this._drawWxmlBlock(item, sorted, all, progress, results[1]);
                all = this._drawWxmlInline(item, sorted, all, progress, results[1]);
                Promise.all(all).then(results => {
                    resolve && resolve();
                }).catch(e => {
                    reject && reject(e);
                });
            });
        } catch (e) {
            reject && reject({
                errcode: 1008,
                errmsg: "drawWxml error"
            });
        }
    }
    _drawWxmlBlock(item, sorted, all, progress, results) {
        let self = this;
        let limitLeft = results ? results.left : 0;
        let limitTop = results ? results.top : 0;
        Object.keys(sorted).forEach((top, topIndex) => {
            let list = sorted[top].sort((a, b) => {
                return a.left - b.left;
            });
            list = list.filter(sub => sub.dataset.type && sub.dataset.type.indexOf("inline") === -1);
            list.forEach((sub, index) => {
                all[index] = new Promise((resolve2, reject2) => {
                    sub = self._transferWxmlStyle(sub, item, limitLeft, limitTop);
                    sub.progress = progress;
                    let type = sub.dataset.type;
                    if (sub.dataset.delay) {
                        setTimeout(() => {
                            drawWxmlItem();
                        }, sub.dataset.delay);
                    } else {
                        drawWxmlItem();
                    }
                    function drawWxmlItem() {
                        if (type === "text") {
                            self._drawWxmlText(sub, resolve2, reject2);
                        } else if (type === "image") {
                            self._drawWxmlImage(sub, resolve2, reject2);
                        } else if (type === "radius-image") {
                            self._drawWxmlCircleImage(sub, resolve2, reject2);
                        } else if (type === "background-image") {
                            self._drawWxmlBackgroundImage(sub, resolve2, reject2);
                        }
                    }
                });
            });
        });
        return all;
    }
    _drawWxmlInline(item, sorted, all, progress, results) {
        let self = this;
        let topOffset = 0;
        let leftOffset = 0;
        let lastTop = 0;
        let limitLeft = results ? results.left : 0;
        let limitTop = results ? results.top : 0;
        let p = new Promise((resolve2, reject2) => {
            let maxWidth = 0;
            let minLeft = Infinity;
            let maxRight = 0;
            Object.keys(sorted).forEach(top => {
                let inlineList = sorted[top].filter(sub => sub.dataset.type && sub.dataset.type.indexOf("inline") > -1);
                inlineList.forEach(sub => {
                    if (sub.left < minLeft) {
                        minLeft = sub.left;
                    }
                    if (sub.right > maxRight) {
                        maxRight = sub.right;
                    }
                });
            });
            maxWidth = Math.ceil(maxRight - minLeft || self.width);
            Object.keys(sorted).forEach((top, topIndex) => {
                let list = sorted[top].sort((a, b) => {
                    return a.left - b.left;
                });
                let position = -1;
                for (let i = 0, len = list.length; i < len; i++) {
                    if (list[i] && list[i + 1]) {
                        if (list[i].bottom > list[i + 1].bottom) {
                            position = i;
                            break;
                        }
                    }
                }
                if (position > -1) {
                    list.push(list.splice(position, 1)[0]);
                }
                let inlineList = list.filter(sub => sub.dataset.type && sub.dataset.type.indexOf("inline") > -1);
                let originLeft = inlineList[0] ? inlineList[0].left : 0;
                if (Math.abs(topOffset + lastTop - top) > 2 || leftOffset - originLeft - limitLeft >= maxWidth || leftOffset <= originLeft - limitLeft - 2) {
                    leftOffset = 0;
                }
                lastTop = +top;
                topOffset = 0;
                inlineList.forEach((sub, index) => {
                    sub = self._transferWxmlStyle(sub, item, limitLeft, limitTop);
                    sub.progress = progress;
                    let type = sub.dataset.type;
                    if (type === "inline-text") {
                        let drawRes = self._drawWxmlInlineText(sub, leftOffset, maxWidth);
                        leftOffset = drawRes.leftOffset;
                        topOffset = drawRes.topOffset;
                    } else if (type === "inline-image") {
                        let drawRes = self._drawWxmlImage(sub) || {};
                        leftOffset = drawRes.leftOffset || 0;
                        topOffset = drawRes.topOffset || 0;
                    }
                });
            });
            resolve2();
        });
        all.push(p);
        return all;
    }
    _drawWxmlInlineText(sub, leftOffset = 0, maxWidth) {
        let text = sub.dataset.text || "";
        if (sub.dataset.maxlength && text.length > sub.dataset.maxlength) {
            text = text.substring(0, sub.dataset.maxlength) + "...";
        }
        let textData = {
            text: text,
            originX: sub.left,
            x: leftOffset ? leftOffset : sub.left,
            y: sub.top,
            progress: sub.progress,
            leftOffset: leftOffset,
            maxWidth: maxWidth
        };
        if (sub.backgroundColor !== "rgba(0, 0, 0, 0)") {
            sub.background = sub.backgroundColor;
        } else {
            sub.background = "rgba(0, 0, 0, 0)";
        }
        if (sub.dataset.background) {
            sub.background = sub.dataset.background;
        }
        let res = this._drawText(textData, sub, null, null, "inline-text", "wxml");
        return res;
    }
    _drawWxmlText(sub, resolve, reject) {
        let text = sub.dataset.text || "";
        if (sub.dataset.maxlength && text.length > sub.dataset.maxlength) {
            text = text.substring(0, sub.dataset.maxlength) + "...";
        }
        let textData = {
            text: text,
            x: sub.left,
            y: sub.top,
            progress: sub.progress
        };
        if (sub.backgroundColor !== "rgba(0, 0, 0, 0)") {
            sub.background = sub.backgroundColor;
        } else {
            sub.background = "rgba(0, 0, 0, 0)";
        }
        if (sub.dataset.background) {
            sub.background = sub.dataset.background;
        }
        this._drawText(textData, sub, resolve, reject, "text", "wxml");
    }
    _drawWxmlImage(sub, resolve, reject) {
        let imageData = {
            url: sub.dataset.url,
            x: sub.left,
            y: sub.top,
            progress: sub.progress
        };
        let res = this._drawRect(imageData, sub, resolve, reject, "image", "inline-wxml");
        return res;
    }
    _drawWxmlCircleImage(sub, resolve, reject) {
        let imageData = {
            url: sub.dataset.url,
            x: sub.left,
            y: sub.top,
            progress: sub.progress
        };
        sub.r = sub.width / 2;
        this._drawCircle(imageData, sub, resolve, reject, true, "wxml");
    }
    _drawWxmlBackgroundImage(sub, resolve, reject) {
        let url = sub.dataset.url;
        let index = this._findPicIndex(url);
        url = index > -1 ? this.allPic[index].local : url;
        let size = sub.backgroundSize.replace(/px/g, "").split(" ");
        let imageData = {
            url: url,
            x: sub.left,
            y: sub.top,
            progress: sub.progress
        };
        this._drawRect(imageData, sub, resolve, reject, "image", "wxml");
    }
    _getWxml(item, style) {
        let self = this;
        let query;
        if (this.obj) {
            query = wx.createSelectorQuery().in(this.obj);
        } else {
            query = wx.createSelectorQuery();
        }
        let p1 = new Promise((resolve, reject) => {
            let count = 0;
            query.selectAll(`${item.class}`).fields({
                dataset: true,
                size: true,
                rect: true,
                computedStyle: [ "width", "height", "font", "fontSize", "fontFamily", "fontWeight", "fontStyle", "textAlign", "color", "lineHeight", "border", "borderColor", "borderStyle", "borderWidth", "verticalAlign", "boxShadow", "background", "backgroundColor", "backgroundImage", "backgroundPosition", "backgroundSize", "paddingLeft", "paddingTop", "paddingRight", "paddingBottom" ]
            }, res => {
                if (count++ === 0) {
                    let formated = self._formatImage(res);
                    let list = formated.list;
                    res = formated.res;
                    self._preloadImage(list).then(result => {
                        resolve(res);
                    }).catch(res => {
                        reject && reject({
                            errcode: 1009,
                            errmsg: "drawWxml preLoadImage error"
                        });
                    });
                }
            }).exec();
        });
        let p2 = new Promise((resolve, reject) => {
            if (!item.limit) {
                resolve({
                    top: 0,
                    width: self.width / self.zoom
                });
            }
            query.select(`${item.limit}`).fields({
                dataset: true,
                size: true,
                rect: true
            }, res => {
                resolve(res);
            }).exec();
        });
        return Promise.all([ p1, p2 ]);
    }
    _getLineHeight(style) {
        let zoom = this.zoom;
        if (style.dataset && style.dataset.type) {
            zoom = 1;
        }
        let lineHeight;
        if (!isNaN(style.lineHeight) && style.lineHeight > style.fontSize) {
            lineHeight = style.lineHeight;
        } else {
            style.lineHeight = (style.lineHeight || "") + "";
            lineHeight = +style.lineHeight.replace("px", "");
            lineHeight = lineHeight ? lineHeight : (style.fontSize || 14) * 1.2;
        }
        return lineHeight * zoom;
    }
    _formatImage(res = []) {
        let list = [];
        res.forEach((item, index) => {
            let dataset = item.dataset;
            let uid = Util.getUid();
            let filename = `${wx.env.USER_DATA_PATH}/${uid}.png`;
            if ((dataset.type === "image" || dataset.type === "radius-image") && dataset.url) {
                let sub = {
                    url: dataset.base64 ? filename : dataset.url,
                    isBase64: dataset.base64 ? dataset.url : false
                };
                res[index].dataset = Object.assign(res[index].dataset, sub);
                list.push(sub);
            } else if (dataset.type === "background-image" && item.backgroundImage.indexOf("url") > -1) {
                let url = item.backgroundImage.replace(/url\((\"|\')?/, "").replace(/(\"|\')?\)$/, "");
                let sub = {
                    url: dataset.base64 ? filename : url,
                    isBase64: dataset.base64 ? url : false
                };
                res[index].dataset = Object.assign(res[index].dataset, sub);
                list.push(sub);
            }
        });
        return {
            list: list,
            res: res
        };
    }
    _updateProgress(distance) {
        this.progressPercent += distance;
        this.progress(this.progressPercent);
    }
    _sortListByTop(list = []) {
        let sorted = {};
        list.forEach((item, index) => {
            let top = item.top;
            if (!sorted[top]) {
                if (sorted[top - 2]) {
                    top = top - 2;
                } else if (sorted[top - 1]) {
                    top = top - 1;
                } else if (sorted[top + 1]) {
                    top = top + 1;
                } else if (sorted[top + 2]) {
                    top = top + 2;
                } else {
                    sorted[top] = [];
                }
            }
            sorted[top].push(item);
        });
        return sorted;
    }
    _parseNumber(number) {
        return isNaN(number) ? +(number || "").replace("px", "") : number;
    }
    _transferWxmlStyle(sub, item, limitLeft, limitTop) {
        let leftFix = +sub.dataset.left || 0;
        let topFix = +sub.dataset.top || 0;
        sub.width = this._parseNumber(sub.width);
        sub.height = this._parseNumber(sub.height);
        sub.left = this._parseNumber(sub.left) - limitLeft + (leftFix + (item.x || 0)) * this.zoom;
        sub.top = this._parseNumber(sub.top) - limitTop + (topFix + (item.y || 0)) * this.zoom;
        let padding = sub.dataset.padding || "0 0 0 0";
        if (typeof padding === "string") {
            padding = Util.transferPadding(padding);
        }
        let paddingTop = Number(sub.paddingTop.replace("px", "")) + Number(padding[0]);
        let paddingRight = Number(sub.paddingRight.replace("px", "")) + Number(padding[1]);
        let paddingBottom = Number(sub.paddingBottom.replace("px", "")) + Number(padding[2]);
        let paddingLeft = Number(sub.paddingLeft.replace("px", "")) + Number(padding[3]);
        sub.padding = [ paddingTop, paddingRight, paddingBottom, paddingLeft ];
        return sub;
    }
    _resetPositionX(item, style) {
        let zoom = this.zoom;
        let x = 0;
        if (style.dataset && style.dataset.type) {
            zoom = 1;
        }
        if (item.x < 0 && item.type) {
            x = this.width + item.x * zoom - style.width * zoom;
        } else {
            x = item.x * zoom;
        }
        if (parseInt(style.borderWidth)) {
            x += parseInt(style.borderWidth);
        }
        return x + this.translateX;
    }
    _resetPositionY(item, style, textHeight) {
        let zoom = this.zoom;
        let y = 0;
        if (style.dataset && style.dataset.type) {
            zoom = 1;
        }
        if (item.y < 0) {
            y = this.height + item.y * zoom - (textHeight ? textHeight : style.height * zoom);
        } else {
            y = item.y * zoom;
        }
        if (parseInt(style.borderWidth)) {
            y += parseInt(style.borderWidth);
        }
        return y + this.translateY;
    }
    _resetTextPositionX(item, style, textWidth, width) {
        let textAlign = style.textAlign || "left";
        let x = item.x;
        if (textAlign === "center") {
            x = (width - textWidth) / 2 + item.x;
        } else if (textAlign === "right") {
            x = width - textWidth + item.x;
        }
        let left = style.padding ? style.padding[3] || 0 : 0;
        return x + left + this.translateX;
    }
    _resetTextPositionY(item, style, lineNum = 0) {
        let zoom = this.zoom;
        if (style.dataset && style.dataset.type) {
            zoom = 1;
        }
        let lineHeight = this._getLineHeight(style);
        let fontSize = Math.ceil((style.fontSize || 14) * zoom);
        let blockLineHeightFix = (style.dataset && style.dataset.type || "").indexOf("inline") > -1 ? 0 : (lineHeight - fontSize) / 2;
        let top = style.padding ? style.padding[0] || 0 : 0;
        return item.y + blockLineHeightFix + lineNum * lineHeight + top + this.translateY;
    }
    _getTextSingleLine(text, width, singleLength, currentIndex = 0, widthOffset = 0) {
        let offset = 0;
        let endIndex = currentIndex + singleLength + offset;
        let single = text.substring(currentIndex, endIndex);
        let singleWidth = this.measureWidth(single);
        while (Math.round(widthOffset + singleWidth) > width) {
            offset--;
            endIndex = currentIndex + singleLength + offset;
            single = text.substring(currentIndex, endIndex);
            singleWidth = this.measureWidth(single);
        }
        return {
            endIndex: endIndex,
            single: single,
            singleWidth: singleWidth
        };
    }
    _drawBorder(border, style, callback) {
        let zoom = this.zoom;
        if (style.dataset && style.dataset.type) {
            zoom = 1;
        }
        border = Util.transferBorder(border);
        if (border && border.width) {
            this._drawBoxShadow();
            if (border) {
                this.ctx.setLineWidth(border.width * zoom);
                if (border.style === "dashed") {
                    let dash = style.dash || [ 5, 5, 0 ];
                    let offset = dash[2] || 0;
                    let array = [ dash[0] || 5, dash[1] || 5 ];
                    this.ctx.setLineDash(array, offset);
                }
                this.ctx.setStrokeStyle(border.color);
            }
            callback && callback(border);
        }
    }
    _drawBoxShadow(boxShadow, callback) {
        boxShadow = Util.transferBoxShadow(boxShadow);
        if (boxShadow) {
            this.ctx.setShadow(boxShadow.offsetX, boxShadow.offsetY, boxShadow.blur, boxShadow.color);
        } else {
            this.ctx.setShadow(0, 0, 0, "#ffffff");
        }
        callback && callback(boxShadow || {});
    }
    _setFill(fill, callback) {
        if (fill) {
            if (typeof fill === "string") {
                this.ctx.setFillStyle(fill);
            } else {
                let line = fill.line;
                let color = fill.color;
                let grd = this.ctx.createLinearGradient(line[0], line[1], line[2], line[3]);
                grd.addColorStop(0, color[0]);
                grd.addColorStop(1, color[1]);
                this.ctx.setFillStyle(grd);
            }
            callback && callback();
        }
    }
    _setStroke(stroke, callback) {
        if (stroke) {
            if (typeof stroke === "string") {
                this.ctx.setStrokeStyle(stroke);
            } else {
                let line = stroke.line;
                let color = stroke.color;
                let grd = this.ctx.createLinearGradient(line[0], line[1], line[2], line[3]);
                grd.addColorStop(0, color[0]);
                grd.addColorStop(1, color[1]);
                this.ctx.setStrokeStyle(grd);
            }
            callback && callback();
        }
    }
}

module.exports = Wxml2Canvas;